package org.sean.framework.schedule.server;

import org.sean.framework.canary.feign.CanaryInfoHolder;
import org.sean.framework.code.StatusInfo;
import org.sean.framework.context.SpringApplicationContext;
import org.sean.framework.exception.StatusException;
import org.sean.framework.logging.Logger;
import org.sean.framework.pool.IOTaskExecutor;
import org.sean.framework.redis.RedisService;
import org.sean.framework.schedule.ScheduleJob;
import org.sean.framework.schedule.client.ScheduleClientImpl;
import org.sean.framework.util.StringUtil;
import org.sean.framework.web.holder.RequestHolder;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * 消息处理器
 *
 * @author xielei
 */
public class ScheduleServerImpl implements ScheduleServer {
    private static final String SYNC_KEY_PRE = "SYNC_SCHEDULE_JOB";
    private final Logger logger = Logger.newInstance(getClass());
    /**
     * 锁续约
     */
    private ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(8);

    /**
     * 执行任务
     */
    @Override
    public String receive(ScheduleJob job) {
        String message = "SUCCESS";
        try {
            Object target = SpringApplicationContext.getBean(job.getBeanName());
            Method method = getMethod(job);
            Map<String, String> headers = RequestHolder.getHeaders();
            String traceId = headers.get(Logger.KEY_TRACE);
            String spanId = headers.get(Logger.KEY_TRACE);
            final String key = SYNC_KEY_PRE + job.getId();
            IOTaskExecutor.getInstance().execute(() -> {
                ScheduledFuture<?> future = null;
                RedisService redisService = SpringApplicationContext.getBean(RedisService.class);
                // 处理灰度配置
                CanaryInfoHolder.handle(headers, traceId, spanId);
                try {
                    // 任务锁
                    if (Boolean.FALSE.equals(job.getASync()) && redisService != null) {
                        redisService.putString(key, key, 1, TimeUnit.MINUTES);

                        future = scheduledExecutor.scheduleAtFixedRate(() -> {
                            // 锁续约
                            if (redisService.getExpire(key) != null) {
                                redisService.expire(key, 1, TimeUnit.MINUTES);
                            }
                        }, 500, 500, TimeUnit.MILLISECONDS);
                    }
                    logger.debug("执行定时任务开始: 任务id:{}", job.getId());
                    String params = job.getParams();
                    if (StringUtil.isBlank(params)) {
                        method.invoke(target);
                    } else {
                        method.invoke(target, params);
                    }
                    logger.debug("执行定时任务成功,任务id:{}", job.getId());
                } catch (Exception e) {
                    logger.error(e);
                    logger.error("执行定时任务失败:{},任务id:{}", e.getMessage(), job.getId());
                } finally {
                    // 取消 锁续约任务
                    if (future != null) {
                        future.cancel(true);
                    }
                    if (redisService != null) {
                        redisService.delete(key);
                    }
                }
                return null;
            }).thenRun(() -> {
                try {
                    CanaryInfoHolder.cleanHeaders();
                } catch (Exception e) {
                    // Do Nothing
                }
            });
        } catch (StatusException e) {
            logger.error(e);
            message = ScheduleClientImpl.FAILED;
        } catch (Exception e) {
            logger.error(e);
            message = ScheduleClientImpl.UNKNOWN;
        }
        return message;
    }

    private Method getMethod(ScheduleJob job) {
        Object target = SpringApplicationContext.getBean(job.getBeanName());
        if (target == null) {
            throw new StatusException(StatusInfo.dataNotExistError().setAnyMessage("Bean不存在"));
        }
        Method method;
        try {
            String params = job.getParams();
            if (StringUtil.isBlank(params)) {
                method = target.getClass().getDeclaredMethod(job.getMethodName());
            } else {
                method = target.getClass().getDeclaredMethod(job.getMethodName(), String.class);
            }
        } catch (Exception e) {
            throw new StatusException(StatusInfo.dataNotExistError().setAnyMessage("Method不存在"));
        }
        ReflectionUtils.makeAccessible(method);
        return method;
    }
}
